#include "app.hpp"
#include <cassert>
#include <iostream>
#include <iomanip>
#include <uuids.h>
#include <evcode.h>
#include "hrtext.hpp"
#include "mediatypeutil.hpp"
#include <sstream>
using std::hex;
using std::dec;
using std::wcout;
using std::endl;
using std::wostringstream;
using GraphUtil::IPinPtr;
using GraphUtil::IBaseFilterPtr;
//using GraphUtil::IFileSourceFilterPtr;
using GraphUtil::FindOutpinVideo;
using GraphUtil::FindOutpinAudio;
using GraphUtil::FindInpinVideo;
using GraphUtil::FindInpinAudio;

App::App(HANDLE hQuit) :
    m_hQuit(hQuit)
{
    assert(m_hQuit);
}


int App::operator()(int argc, wchar_t* argv[])
{
    int status = m_cmdline.Parse(argc, argv);
    
    if (status)
        return status;
        
    status = CreateGraph();
    
    if (status)
        return status;
        
    status = RunGraph();
    
    DestroyGraph();
    
    return status;
}


int App::CreateGraph()
{
    assert(!bool(m_pGraph));
    assert(!bool(m_pSeek));
    
    HRESULT hr = m_pGraph.CreateInstance(CLSID_FilterGraphNoThread);
    
    if (FAILED(hr))
    {
        wcout << L"Unable to create filter graph instance.\n"
              << hrtext(hr)
              << " (0x" << hex << hr << dec << ")"
              << endl;
              
        return 1;  //error
    }
    
    assert(bool(m_pGraph));
    
    const GraphUtil::IMediaFilterPtr pGraphFilter(m_pGraph);
    assert(bool(pGraphFilter));
    
    //IMediaFilter::SetSyncSource
    //ms-help://MS.VSCC.2003/MS.MSDNQTR.2003FEB.1033/directshow_sp1/htm/imediafiltersetsyncsource.htm
    //
    //Reference Clocks
    //ms-help://MS.VSCC.2003/MS.MSDNQTR.2003FEB.1033/directshow_sp1/htm/referenceclocks.htm
    
    hr = pGraphFilter->SetSyncSource(0);  //process as quickly as possible
    //TODO: are we setting this too early?
    
#ifdef _DEBUG
    if (FAILED(hr))
    {
        wcout << L"IMediaFilter::SetSyncSource failed.\n"
              << hrtext(hr)
              << L" (0x" << hex << hr << dec << L")"
              << endl;
    }
#endif

    IBaseFilterPtr pReader;
    
    hr = pReader.CreateInstance(m_cmdline.GetReader());
    
    if (FAILED(hr))
    {
        wcout << "Unable to create reader filter instance.\n"
              << hrtext(hr)
              << L" (0x" << hex << hr << dec << L")"
              << endl;
              
        return 1;
    }
    
    assert(bool(pReader));
    assert(GraphUtil::PinCount(pReader) == 1);
    
    hr = m_pGraph->AddFilter(pReader, L"reader");
    assert(SUCCEEDED(hr));
    
    const GraphUtil::IFileSourceFilterPtr pFileSource(pReader);
    assert(bool(pFileSource));
    
    hr = pFileSource->Load(m_cmdline.GetInputFileName(), 0);
    
    if (FAILED(hr))
    {
        wcout << "Unable to load input file.\n"
              << hrtext(hr)
              << L" (0x" << hex << hr << dec << L")"
              << endl;
             
        return 1;
    }
    
    IBaseFilterPtr pDemux;
    
    hr = pDemux.CreateInstance(m_cmdline.GetSplitter());
    
    if (FAILED(hr))
    {
        wcout << "Unable to create splitter filter instance.\n"
              << hrtext(hr)
              << L" (0x" << hex << hr << dec << L")"
              << endl;
              
        return 1;
    }
    
    assert(bool(pDemux));
    assert(GraphUtil::InpinCount(pDemux) == 1);

    hr = m_pGraph->AddFilter(pDemux, L"splitter");
    assert(SUCCEEDED(hr));
    
    AM_MEDIA_TYPE mt;
    memset(&mt, 0, sizeof mt);
    mt.majortype = MEDIATYPE_Stream;
    mt.subtype = MEDIASUBTYPE_MPEG2_PROGRAM;
    
    hr = GraphUtil::ConnectDirect(m_pGraph, pReader, pDemux, &mt);
    
    if (FAILED(hr))
    {
        wcout << "Unable to connect reader to splitter.\n"
              << hrtext(hr) 
              << L" (0x" << hex << hr << dec << L")"
              << endl;
              
        return 1;
    }
    
    const IPinPtr pDemuxOutpinVideo(FindOutpinVideo(pDemux));
    //TODO: we need to do better here: we check for the 0 case,
    //but we must also check for the 1+ case.
    
    //if (!bool(pDemuxOutpinVideo))
    //{
    //    wcout << "Splitter has no video outpin." << endl;
    //    return 1;
    //}
    
    const IPinPtr pDemuxOutpinAudio(FindOutpinAudio(pDemux));
    //TODO: we need to do better here: we check for the 0 case,
    //but we must also check for the 1+ case.
    
    //if (!bool(pDemuxOutpinAudio))
    //{
    //    wcout << "Splitter has no audio outpin." << endl;
    //    return 1;
    //}

    IBaseFilterPtr pMux;
    
    hr = pMux.CreateInstance(L"MJH.MkvMux");
    
    if (FAILED(hr))
    {
        wcout << "Unable to create MkvMux filter instance.\n"
              << hrtext(hr)
              << L" (0x" << hex << hr << dec << L")"
              << endl;
              
        return 1;
    }
    
    assert(GraphUtil::InpinCount(pMux) == 2);  //TODO: liberalize
    assert(GraphUtil::OutpinCount(pMux) == 1);
    
    hr = m_pGraph->AddFilter(pMux, L"mkvmux");
    assert(SUCCEEDED(hr));
    
    int nConnections = 0;
    
    if (bool(pDemuxOutpinVideo))
    {
        const IPinPtr pMuxInpinVideo(FindInpinVideo(pMux));
        assert(bool(pMuxInpinVideo));
    
        hr = m_pGraph->ConnectDirect(pDemuxOutpinVideo, pMuxInpinVideo, 0);
        
        if (FAILED(hr))
        {
            wcout << "Unable to connect splitter video outpin to muxer video inpin.\n"
                  << hrtext(hr)
                  << L" (0x" << hex << hr << dec << L")"
                  << endl;
                          
            return 1;
        }
        
        ++nConnections;
    }
    
    if (bool(pDemuxOutpinAudio))
    {
        const IPinPtr pMuxInpinAudio(FindInpinAudio(pMux));
        assert(bool(pMuxInpinAudio));
        
        hr = m_pGraph->ConnectDirect(pDemuxOutpinAudio, pMuxInpinAudio, 0);
        
        if (FAILED(hr))
        {
            wcout << "Unable to connect splitter audio outpin to muxer audio inpin.\n"
                  << hrtext(hr)
                  << L" (0x" << hex << hr << dec << L")"
                  << endl;

            return 1;
        }
        
        ++nConnections;
    }
    
    if (nConnections <= 0)
    {
        wcout << L"No splitter outpins are connected to muxer inpins." << endl;
        return 1;
    }
        
    IBaseFilterPtr pWriter;
    
    hr = pWriter.CreateInstance(CLSID_FileWriter);
    
    if (FAILED(hr))
    {
        wcout << "Unable to create writer filter instance.\n"
              << hrtext(hr)
              << L" (0x" << hex << hr << dec << L")"
              << endl;
              
        return 1;
    }
    
    assert(bool(pWriter));
    assert(GraphUtil::InpinCount(pWriter) == 1);
    
    m_pGraph->AddFilter(pWriter, L"writer");
    assert(SUCCEEDED(hr));
    
    const GraphUtil::IFileSinkFilterPtr pSink(pWriter);
    assert(bool(pSink));
    
    hr = pSink->SetFileName(m_cmdline.GetOutputFileName(), 0);
    
    if (FAILED(hr))
    {
        wcout << "Unable to set output filename of file writer filter.\n"
              << hrtext(hr)
              << L" (0x" << hex << hr << dec << L")"
              << endl;
        
        return 1;
    }
    
    hr = GraphUtil::ConnectDirect(m_pGraph, pMux, pWriter, 0);
    
    if (FAILED(hr))
    {
        wcout << "Unable to connect muxer to writer.\n"
              << hrtext(hr)
              << L" (0x" << hex << hr << dec << L")"
              << endl;
              
        return 1;
    }
    
    m_pSeek = pMux;
    assert(bool(m_pSeek));
    
    return 0;  //success   
}



void App::DestroyGraph()
{
    m_pSeek = 0;
    
    if (IFilterGraph* pGraph = m_pGraph.Detach())
    {
        const ULONG n = pGraph->Release();
        n;
        assert(n == 0);
    }
}


int App::RunGraph()
{
    assert(bool(m_pGraph));
    
    const GraphUtil::IMediaEventPtr pEvent(m_pGraph);
    assert(bool(pEvent));

    HANDLE h;
    
    HRESULT hr = pEvent->GetEventHandle((OAEVENT*)&h);
    assert(hr == S_OK);
    assert(h);
    
    //DWORD dw = WaitForSingleObject(h, 0);
    
    enum { nh = 2 };
    const HANDLE ha[nh] = { m_hQuit, h };
    
    const GraphUtil::IMediaControlPtr pControl(m_pGraph);
    assert(bool(pControl));
    
    hr = pControl->Run();
    assert(SUCCEEDED(hr));
    
    //int n = 1;
    
    for (;;)
    {
        MSG msg;
        
        while (PeekMessage(&msg, 0, 0, 0, PM_REMOVE))
        {
            assert(msg.message != WM_QUIT);
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
        
        const DWORD dw = MsgWaitForMultipleObjects(
                            nh,
                            ha,
                            0,
                            100,  //timeout (ms)
                            QS_ALLINPUT);
                    
        if (dw == WAIT_TIMEOUT)
        {
            //TODO: display this (or give option to) in HH:MM:SS.sss
            //TODO: attempt to query upstream filter for duration.
            
            if (bool(m_pSeek))
            {
                __int64 curr;

                hr = m_pSeek->GetCurrentPosition(&curr);

                if (FAILED(hr))
                {
#ifdef _DEBUG
                    wcout << L"IMediaSeeking::GetCurrPos failed: "
                          << hrtext(hr)
                          << L" (0x" << hex << hr << dec << L")"
                          << endl;
#endif
                    continue;                    
                }
                    
                assert(curr >= 0);
                
                const double t = double(curr) / 10000000;
                
                wostringstream os;

                os << std::fixed << std::setprecision(1) << t;
                
                if (m_cmdline.ScriptMode())
                    wcout << "TIME=" << os.str() << endl;
                else
                    wcout << "\rtime[sec]=" << os.str() << std::flush;
            }
            
            continue;
        }

        assert(dw >= WAIT_OBJECT_0);
        assert(dw <= (WAIT_OBJECT_0 + nh));
        
        if (dw == WAIT_OBJECT_0)  //quit
        {
            //wcout << "CTRL+C detected" << endl;
            break;
        }
            
        if (dw == (WAIT_OBJECT_0 + nh))  //window message
            continue;
            
        //media event
        
        long code, param1, param2;
        
        HRESULT hr = pEvent->GetEvent(&code, &param1, &param2, 0);
        assert(hr == S_OK);
        
        hr = pEvent->FreeEventParams(code, param1, param2);
        assert(hr == S_OK);
        
        if (code == EC_USERABORT) //window closed
        {
            //wcout << "EC_USERABORT" << endl;
            break;
        }
            
        if (code == EC_COMPLETE)
        {
            //wcout << "EC_COMPLETE" << endl;
            break;
        }
            
        //if (code == (EC_USER + 0x100))  //done muxing
        //    break;
    }

    if (!m_cmdline.ScriptMode())
        wcout << endl;
    
    hr = pControl->Stop();
    assert(SUCCEEDED(hr));
    
    return 0;
}
